﻿namespace PokerSimulator
{
	using System;
	using System.Collections.Generic;
	using System.Globalization;
	using System.Linq;
	using System.Text;

	public sealed class Hand
	{
		private readonly int[] _fullHouse;
		private readonly List<Card> _hand;
		private readonly List<Card> _thPocket;
		private readonly int[] _twoPairHolder;
		private string _description;
		private char _flushSuit;
		private int _fourKind;
		private double _fullHouseValue;
		private int _highCardInStraight;
		private double _moneyCard;
		private int _singlePair;
		private bool _stillPlaying = true;
		private int[] _tally;
		private int _threeKind;
		private double _value;

		public Hand()
		{
			_twoPairHolder = new int[2];
			_fullHouse = new int[2];
			_hand = new List<Card>(7);
			_thPocket = new List<Card>(2);
		}

		public double MoneyCard
		{
			get { return _moneyCard; }
			set { _moneyCard = value; }
		}

		public bool StillPlaying
		{
			get { return _stillPlaying; }
			set { _stillPlaying = value; }
		}

		public double Value
		{
			get { return _value; }
			set { _value = value; }
		}

		public string Description
		{
			get
			{
				if (string.IsNullOrEmpty(_description))
				{
					_description = HandValue.GetDescription(this);
				}
				return _description;
			}
		}

		public int HighCardInStraight
		{
			get { return _highCardInStraight; }
		}


		public int CardCount
		{
			get { return _hand.Count; }
		}

		public bool IsEmpty
		{
			get { return (null == _hand) || (0 == _hand.Count); }
		}

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();
			_hand.ForEach(c => sb.Append(c).Append(','));
			sb.Remove(sb.Length - 1, 1);
			return sb.ToString();
		}

		public int[] GetTwoPair()
		{
			return _twoPairHolder;
		}

		public int[] GetFullHouse()
		{
			return _fullHouse;
		}


		public void Clear()
		{
			_hand.Clear();
		}

		private void AddValue(int plus)
		{
			string newValue;
			if ((int)Value == Value)
			{
				newValue = string.Concat(Value.ToString(NumberFormatInfo.InvariantInfo), ".", plus.ToString(NumberFormatInfo.InvariantInfo).PadLeft(2, '0'));
			}
			else
			{
				newValue = string.Concat(Value.ToString(NumberFormatInfo.InvariantInfo), plus.ToString(NumberFormatInfo.InvariantInfo).PadLeft(2, '0'));
			}
			Value = double.Parse(newValue, NumberFormatInfo.InvariantInfo);
		}

		public void AddCard(Card card)
		{
			_hand.Add(card);
		}

		private static Comparison<Card> CardValueComparison()
		{
			return ((a, b) => a.Value.CompareTo(b.Value));
		}

		public void Fold()
		{
			Value = 0;
			StillPlaying = false;
			_description = string.Concat("Folded: ", _hand[0], " & ", _hand[1]);
		}


		private int GetHighCardValue()
		{
			return _hand[_hand.Count - 1].Value;
		}


		private static int GetNKind(int quantity, Hand hand)
		{
			return GetNKind(quantity, hand, NKindType.Max);
		}

		private static int GetNKind(int quantity, Hand hand, NKindType type)
		{
			GetHandTally(ref hand);
			int[] tally = hand._tally;
			int rtn = CardValue.NullValue;

			if (type == NKindType.Max)
			{
				rtn = GetNKindMaxInternal(quantity, tally, rtn);
			}
			else if (type == NKindType.Min)
			{
				rtn = GetNKindMinInternal(quantity, tally, rtn);
			}
			return rtn;
		}

		private static void GetHandTally(ref Hand hand)
		{
			if (hand._tally == null)
			{
				hand._tally = new int[13];
				foreach (Card c in hand._hand)
				{
					hand._tally[c.Value - 2]++;
				}
			}
		}


		private static int GetNKindMaxInternal(int quantity, IList<int> tally, int defaultReturnValue)
		{
			for (int downCount = 12; downCount >= 0; downCount--)
			{
				if (tally[downCount] == quantity)
				{
					return downCount + 2;
				}
			}
			return defaultReturnValue;
		}

		private static int GetNKindMinInternal(int quantity, IList<int> tally, int defaultReturnValue)
		{
			int count = tally.Count;
			for (int upCount = 0; upCount < count; upCount++)
			{
				if (tally[upCount] == quantity)
				{
					return upCount + 2;
				}
			}
			return defaultReturnValue;
		}


		public bool HasTwoPair()
		{
			int top = GetNKind(2, this, NKindType.Max);
			if (top == 0)
			{
				return false;
			}
			int bottom = GetNKind(2, this, NKindType.Min);
			if (top == bottom)
			{
				return false;
			}
			if ((top == CardValue.NullValue) || (bottom == CardValue.NullValue))
			{
				return false;
			}
			_twoPairHolder[0] = top;
			_twoPairHolder[1] = bottom;
			return true;
		}


		public bool HasStraightFlush()
		{
			if (!HasStraight())
			{
				return false;
			}
			if (!HasFlush())
			{
				return false;
			}
			int counter = 5;
			for (int i = _highCardInStraight; counter > 0; i--)
			{
				if (false == CardExistsInHand(i, _flushSuit))
				{
					return false;
				}
				counter--;
			}
			return true;
		}

		private bool CardExistsInHand(int value, char flushSuit)
		{
			return _hand.Exists(c => ((c.Value == value) && (c.Suit == flushSuit)));
		}

		private bool HasFourOfAKind()
		{
			_fourKind = GetNKind(4, this);
			return (_fourKind > CardValue.NullValue);
		}

		private int GetFlushHighCard(char suit)
		{
			for (int i = _hand.Count - 1; i >= 0; i--)
			{
				if (_hand[i].Suit == suit)
				{
					return _hand[i].Value;
				}
			}
			return CardValue.NullValue;
		}

		private int GetStraightHighCard()
		{
			if (_highCardInStraight > 0)
			{
				return _highCardInStraight;
			}

			Hand foo = this;
			GetHandTally(ref foo);
			_highCardInStraight = CheckStraight(foo._tally);
			return _highCardInStraight;
		}

		private bool HasStraight()
		{
			return GetStraightHighCard() > CardValue.NullValue;
		}

		private static int CheckStraight(IEnumerable<int> tally)
		{
			List<int> reversed = new List<int>(tally);
			reversed.Reverse();
			// special case: manually check for A-5
			if (HasAWheel(reversed))
			{
				return 5;
			}
			int count = 0;
			for (int i = 0; i < 13; i++)
			{
				if (count == 5)
				{
					return (18 - i + 1); // wtf?
				}
				int val = reversed[i];
				if (val == 1)
				{
					count++;
				}
				else if (val == 0)
				{
					count = 0;
				}
			}
			return 0;
		}

		private static bool HasAWheel(IList<int> tally)
		{
			return ((tally[0] >= 1) &&
			        (tally[9] >= 1) &&
			        (tally[10] >= 1) &&
			        (tally[11] >= 1) &&
			        (tally[12] >= 1));
		}

		private bool HasFlush()
		{
			char[] suits = Statics.Suits;
			for (int s = 0; s < 4; s++)
			{
				char suit = suits[s];
				int suitCount = _hand.Count(card => (card.Suit == suit));
				if (suitCount > 4)
				{
					_flushSuit = suit;
					return true;
				}
			}
			return false;
		}

		private bool HasFullHouse()
		{
			if (_hand.Count < 5)
			{
				return false;
			}
			int trips = GetNKind(3, this);
			if (trips == CardValue.NullValue)
			{
				return false;
			}
			_hand.Reverse();
			int pair = GetNKind(2, this);
			_hand.Sort(CardValueComparison()); // don't fuck with this
			_fullHouseValue = Convert.ToDouble(trips + "." + pair.ToString(NumberFormatInfo.InvariantInfo).PadLeft(2, '0'), NumberFormatInfo.InvariantInfo);
			_fullHouse[0] = trips;
			_fullHouse[1] = pair;
			return ((pair != CardValue.NullValue) && (pair != trips));
		}

		private bool HasPair()
		{
			_singlePair = GetNKind(2, this);
			return (_singlePair > CardValue.NullValue);
		}

		private bool HasThreeOfAKind()
		{
			if (_hand.Count < 3)
			{
				return false;
			}
			_threeKind = GetNKind(3, this);
			return (_threeKind > CardValue.NullValue);
		}

		public bool TexasHoldEmPreFlopDrop()
		{
			// if J or better stay
			// if paired, stay
			// if suited, stay
			// if connnected, stay
			// else, fold     
			Card low = _hand[0];
			Card high = _hand[1];
			_thPocket.Add(low);
			_thPocket.Add(high);

			if (high.Value > CardValue.TenValue)
			{
				return false;
			}
			if (low.Value == high.Value)
			{
				return false;
			}
			if (low.Suit == high.Suit)
			{
				return false;
			}

			return high.Value != (low.Value + 1);
		}

		public void UpdateValue(GameType gameType)
		{
			if (gameType == GameType.TexasHoldEm)
			{
				UpdateValueForTexasHoldem();
			}
			else
			{
				throw new NotSupportedException(gameType.ToString());
			}
		}

		private void UpdateValueForTexasHoldem()
		{
			double primaryCardValue;
			_hand.Sort(CardValueComparison()); // don't fuck with this
			if (HasStraightFlush())
			{
				primaryCardValue = _highCardInStraight;
				Value = HandValue.Scalar.StraightFlush * primaryCardValue;
				return;
			}
			if (HasFourOfAKind())
			{
				primaryCardValue = _fourKind;
				MoneyCard = primaryCardValue;
				Value = HandValue.Scalar.FourOfAKind * primaryCardValue;
				AppendTieBreakerValue(1, false, primaryCardValue);
				return;
			}
			if (HasFullHouse())
			{
				primaryCardValue = _fullHouseValue;
				Value = HandValue.Scalar.FullHouse * primaryCardValue;
				return;
			}
			if (HasFlush())
			{
				primaryCardValue = GetFlushHighCard(_flushSuit);
				MoneyCard = primaryCardValue;
				Value = HandValue.Scalar.Flush * primaryCardValue;
				AppendTieBreakerValue(4, true);
				return;
			}
			if (HasStraight())
			{
				primaryCardValue = _highCardInStraight;
				MoneyCard = primaryCardValue;
				Value = HandValue.Scalar.Straight * primaryCardValue;
				return;
			}
			if (HasThreeOfAKind())
			{
				primaryCardValue = _threeKind;
				Value = HandValue.Scalar.ThreeOfAKind * primaryCardValue;
				AppendTieBreakerValue(2, false, primaryCardValue);
				return;
			}
			if (HasTwoPair())
			{
				Value = (_twoPairHolder[0] * HandValue.Scalar.TwoPair) + (_twoPairHolder[1] * HandValue.Scalar.TwoPair);
				AppendTieBreakerValue(1, false);
				return;
			}
			if (HasPair())
			{
				primaryCardValue = _singlePair;
				MoneyCard = primaryCardValue;
				Value = HandValue.Scalar.Pair * primaryCardValue;
				AppendTieBreakerValue(3, false, primaryCardValue);
				return;
			}
			primaryCardValue = GetHighCardValue();
			Value = primaryCardValue;
			MoneyCard = primaryCardValue;
			AppendTieBreakerValue(4, false, primaryCardValue);
		}

		private void AppendTieBreakerValue(int numToAdd, bool checkSuit)
		{
			AppendTieBreakerValue(numToAdd, checkSuit, 0.0);
		}

		private void AppendTieBreakerValue(int numToAdd, bool checkSuit, double disallowed)
		{
			int i = _hand.Count - 1;
			int workingNumber = numToAdd;
			Card card;
			while (workingNumber > 0)
			{
				card = _hand[i];
				int cardValue = card.Value;
				char cardSuit = card.Suit;
				if (cardValue != disallowed)
				{
					if (_twoPairHolder.Length == 2)
					{
						if ((cardValue != _twoPairHolder[1]) && (cardValue != _twoPairHolder[0]))
						{
							if (checkSuit)
							{
								if (IsFlushSuit(cardSuit))
								{
									AddValue(cardValue);
								}
							}
							else
							{
								AddValue(cardValue);
							}
							workingNumber--;
						}
					}
					else if (cardValue != MoneyCard)
					{
						if (checkSuit)
						{
							if (IsFlushSuit(cardSuit))
							{
								AddValue(cardValue);
							}
						}
						else
						{
							AddValue(cardValue);
						}
						workingNumber--;
					}
				}
				i--;
			}
		}

		private bool IsFlushSuit(char suit)
		{
			return (suit == _flushSuit);
		}

		public void FoldPreFlop()
		{
			if (TexasHoldEmPreFlopDrop())
			{
				Fold();
			}
		}

		#region Nested type: NKindType

		private enum NKindType
		{
			Min = 1,
			Max = 2
		}

		#endregion
	}
}